﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Management;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MissingLinq.Linq2Management.Context;
using MissingLinq.Linq2Management.Model.CIMv2;

namespace MissingLinq.Linq2Management.Test.Context
{
	[TestClass]
	public class QueryTranslatorWhereUnitTest
	{
		[TestMethod]
		public void BinaryConstantWhereWithString()
		{
			var translator = new QueryTranslator("Win32_Share");

			String query = translator.Translate(Where<Win32Share>(s => s.Name != "Test"));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Name <> 'Test')");

			query = translator.Translate(Where<Win32Share>(s => s.Name == "Test"));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Name = 'Test')");
		}

		[TestMethod]
		public void BinaryConstantWhereWithNumeric()
		{
			/*
			var translator = new QueryTranslator("Win32_Process");

			String query = translator.Translate(Where<Win32Process>(p => p.ProcessId == 1));
			Assert.AreEqual(query, "SELECT * FROM Win32_Process WHERE (ProcessId = 1)");

			query = translator.Translate(Where<Win32Process>(p => p.ProcessId != 1));
			Assert.AreEqual(query, "SELECT * FROM Win32_Process WHERE (ProcessId <> 1)");

			query = translator.Translate(Where<Win32Process>(p => p.ProcessId > 1));
			Assert.AreEqual(query, "SELECT * FROM Win32_Process WHERE (ProcessId > 1)");

			query = translator.Translate(Where<Win32Process>(p => p.ProcessId < 1));
			Assert.AreEqual(query, "SELECT * FROM Win32_Process WHERE (ProcessId < 1)");

			query = translator.Translate(Where<Win32Process>(p => p.ProcessId >= 1));
			Assert.AreEqual(query, "SELECT * FROM Win32_Process WHERE (ProcessId >= 1)");

			query = translator.Translate(Where<Win32Process>(p => p.ProcessId <= 1));
			Assert.AreEqual(query, "SELECT * FROM Win32_Process WHERE (ProcessId <= 1)");*/
		}

		[TestMethod]
		public void BinaryConstantWhereWithEnum()
		{
			var translator = new QueryTranslator("Win32_Share");

			String query = translator.Translate(Where<Win32Share>(s => s.Type == Win32ShareType.DiskDrive));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Type = 0)");

			query = translator.Translate(Where<Win32Share>(s => s.Type != Win32ShareType.DiskDrive));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Type <> 0)");

			query = translator.Translate(Where<Win32Share>(s => s.Type > Win32ShareType.DiskDrive));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Type > 0)");

			query = translator.Translate(Where<Win32Share>(s => s.Type < Win32ShareType.DiskDrive));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Type < 0)");

			query = translator.Translate(Where<Win32Share>(s => s.Type >= Win32ShareType.DiskDrive));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Type >= 0)");

			query = translator.Translate(Where<Win32Share>(s => s.Type <= Win32ShareType.DiskDrive));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (Type <= 0)");
		}

		[TestMethod]
		public void BinaryConstantWhereWithAndOrAlso()
		{
			var translator = new QueryTranslator("Win32_Share");

			String query = translator.Translate(Where<Win32Share>(s => s.Name != "Test" && s.Name != "Test2"));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE ((Name <> 'Test') AND (Name <> 'Test2'))");

			query = translator.Translate(Where<Win32Share>(s => s.Name == "Test" || s.Name == "Test2"));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE ((Name = 'Test') OR (Name = 'Test2'))");

			query = translator.Translate(Where<Win32Share>(s => s.Name != "Test" && s.Name != "Test2" && s.Name != "Test3"));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (((Name <> 'Test') AND (Name <> 'Test2')) AND (Name <> 'Test3'))");

			query = translator.Translate(Where<Win32Share>(s => s.Name == "Test" || s.Name == "Test2" || s.Name == "Test3"));
			Assert.AreEqual(query, "SELECT * FROM Win32_Share WHERE (((Name = 'Test') OR (Name = 'Test2')) OR (Name = 'Test3'))");
		}

		[TestMethod]
		public void Test2()
		{
			using (var mc = new ManagementObjectContext())
			{
				var objs = mc.CIMv2.Win32Shares.ForUpdate().ToList();
				foreach (var obj in objs)
					System.Diagnostics.Debug.WriteLine(obj.Name);
			}
		}

		[TestMethod]
		public void Test()
		{
			var mc = new ManagementClass("Win32_Share");
			mc.Options.UseAmendedQualifiers = true;
			var className = "Win32Share";

			foreach (var method in mc.Methods.Cast<MethodData>())
			{
				var isStatic = method.Qualifiers.Cast<QualifierData>().Any(q => q.Name == "Static" && IsTrue(q.Value));

				var returnValues = new List<String>();
				if (method.OutParameters != null)
					returnValues.AddRange(method.OutParameters.Properties.Cast<PropertyData>().Select(returnParam => returnParam.Type.ToString()));

				var parameters = new List<Parameter>();
				if (method.InParameters != null)
				{
					foreach (var param in method.InParameters.Properties.Cast<PropertyData>())
					{
						var p = new Parameter { OriginalName = param.Name, Name = param.Name, IsArray = param.IsArray };
						foreach (var q in param.Qualifiers.Cast<QualifierData>())
						{
							switch (q.Name)
							{
								case "ID":
									p.Ordinal = (int)q.Value;
									break;
								case "CIMTYPE":
									p.Type = ConvertType(q.Value);
									break;
								case "In":
									p.Output = !IsTrue(q.Value);
									break;
								case "Optional":
									p.Optional = IsTrue(q.Value);
									break;
							}
						}

						parameters.Add(p);
					}
				}

				var returnValue = returnValues.Count == 0 ? "void" : (returnValues.Count > 1 ? String.Format("Tuple<{0}>", String.Join(",", returnValues)) : returnValues[0]);

				bool encounteredOptional = false;
				var paramPrototype = new StringBuilder();

				// put output parameters last, then sort by ordinal
				foreach (var param in parameters.OrderByDescending(p => p.Output).ThenBy(p => p.Ordinal))
				{
					if (param.Optional && !encounteredOptional)
						encounteredOptional = true;

					if (paramPrototype.Length != 0)
						paramPrototype.Append(", ");

					if (param.Output)
						paramPrototype.Append("out ");

					paramPrototype.Append(param.Type);
					paramPrototype.Append(param.IsArray ? "[] " : " ");
					paramPrototype.Append(param.Name);

					if (encounteredOptional)
						paramPrototype.AppendFormat(" = default(typeof({0}))", param.Type);
				}

				System.Diagnostics.Debug.WriteLine("public {0}{1} {2}({3})", isStatic ? "static " : String.Empty, returnValue, method.Name, paramPrototype);
				System.Diagnostics.Debug.WriteLine("{");
				System.Diagnostics.Debug.WriteLine("    var mo = new ManagementObject(\"Win32_Share\");");

				if (parameters.Count != 0)
				{
					System.Diagnostics.Debug.WriteLine("    var mp = mo.GetMethodParameters({0});", (object)method.Name);

					foreach (var param in parameters)
						System.Diagnostics.Debug.WriteLine("    mp[\"{0}\"] = {1};", (object)param.OriginalName, (object)param.Name);
				}

				System.Diagnostics.Debug.WriteLine("    {0}mo.InvokeMethod(\"{1}\", {2});",
						(returnValues.Count > 0 ? String.Format("return ({0})", returnValues[0]) : String.Empty), method.Name, (parameters.Count > 0 ? "mp" : "null"));

				System.Diagnostics.Debug.WriteLine("}");
			}
		}

		private String ConvertType(object type)
		{
			var typeString = type.ToString();
			if (typeString.StartsWith("object:"))
				return (typeString.Split(':')[1]).Replace("_", String.Empty);

			return typeString;
		}

		private bool IsTrue(object value)
		{
			return String.Compare(value.ToString(), "true", true, CultureInfo.InvariantCulture) == 0;
		}

		private Expression Where<TSource>(Expression<Func<TSource, Boolean>> expression)
		{
			return (new MockQueryable<TSource>().Where(expression)).Expression;
		}
	}

	class Parameter
	{
		public String OriginalName;
		public String Name;
		public bool IsArray;
		public bool Optional;
		public bool Output;
		public String Type;
		public int Ordinal;
	}
}
